package cn.cai.pethome.logininfo.service.impl;

import cn.cai.pethome.base.dto.LogininfoDto;
import cn.cai.pethome.base.util.AjaxResult;
import cn.cai.pethome.base.util.JwtUtils;
import cn.cai.pethome.base.util.MD5Utils;
import cn.cai.pethome.base.util.RsaUtils;
import cn.cai.pethome.emp.domain.Employee;
import cn.cai.pethome.emp.mapper.IEmployeeMapper;
import cn.cai.pethome.logininfo.domain.Logininfo;
import cn.cai.pethome.logininfo.mapper.LogininfoMapper;
import cn.cai.pethome.logininfo.service.ILogininfoService;
import cn.cai.pethome.base.service.impl.BaseServiceImpl;
import cn.cai.pethome.system.domain.Menu;
import cn.cai.pethome.system.domain.Permission;
import cn.cai.pethome.system.mapper.PermissionMapper;
import cn.cai.pethome.system.service.IMenuService;
import cn.cai.pethome.user.domain.User;
import cn.cai.pethome.user.dto.UserDto;
import cn.cai.pethome.user.mapper.UserMapper;
import cn.cai.pethome.wechat.domain.Wxuser;
import cn.cai.pethome.wechat.dto.TokenResultDto;
import cn.cai.pethome.wechat.dto.UserinfoDto;
import cn.cai.pethome.wechat.mapper.WxuserMapper;
import cn.cai.pethome.wechat.service.IWxuserService;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.assertj.core.error.ShouldAccept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;


import javax.servlet.http.HttpSession;
import java.security.PrivateKey;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author xiaocai
 * @since 2022-08-27
 */
@Service
public class LogininfoServiceImpl extends BaseServiceImpl<Logininfo> implements ILogininfoService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private LogininfoMapper logininfoMapper;

    @Autowired
    private IEmployeeMapper employeeMapper;

    @Autowired
    private IMenuService menuService;

    @Autowired
    private PermissionMapper permissionMapper;

    @Value("${JWT.privateKey}")
    private String privateKey;


    /**
     * 用户登录需要实现的代码
     * @param userDto  登录需要的对象
     * @param
     * @return
     */
    @Override
    public AjaxResult accountLogin(UserDto userDto) {


            //校验参数
            Assert.isTrue(StringUtils.hasText(userDto.getAccount()),"账号不能为空");
            Assert.isTrue(StringUtils.hasText(userDto.getPassword()),"密码不能为空");

            //根据账号查询logininfo表的数据
            Logininfo logininfo = logininfoMapper.findByAccount(userDto.getAccount());

            //判断账号
            Assert.isTrue(logininfo != null,"账号不存在，请注册！！！");

            //判断密码
            String md5Pssword = MD5Utils.encrypByMd5(userDto.getPassword() + logininfo.getSalt());
            System.out.println("小菜" + md5Pssword);
            System.out.println("菜菜" + logininfo.getPassword());
            Assert.isTrue(logininfo.getPassword().equals(md5Pssword),"账号或者密码错误！！！");
        try {
            return loginSuccess(logininfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return AjaxResult.error("登录失败！！！");

    }

    /**
     * 登录成功的处理  提出的公共部分,弄成了一个方法，ctrl+alt+m
     * @param logininfo
     * @return
     * @throws JsonProcessingException
     */
    private AjaxResult loginSuccess(Logininfo logininfo) throws Exception {
        //登录成功 ，把当前登录用户信息保存在Session中
        logininfo.setPassword(null); //清空密码，安全

        /**
        * 登录成功后
        *  生成一个UUID作为key，将当前登录用户对象作为值，存储Redis中，30分钟过期
        *      如何将一个对象作为值存储到Redis中？
        *          首先要明确Redis的value有哪些数据类型？
        *          string、hash这两种类型是可选的
        *              【这里推荐】可以采用string类型存储：将对象先序列化成json字符串；
        *              可以采用hash类型存储：hash本身是key/value形式，对象的属性名称与属性值也是key/value形式
        *
        *  将UUID和当前登录用户对象一起返回给前端
        *      封装到一个HashMap里面返回
        */
        //生成token -》其实是redis中存储当前登录用户信息的key
       /* String token = UUID.randomUUID().toString();
        System.out.println("token=>" + token);
        //将当前登录用户对象转化为json字符串
        //String jsonString = JSON.toJSONString(logininfo);//阿里巴巴的包 不用try/catch
        String jsonString = new ObjectMapper().writeValueAsString(logininfo);
        //存储到Redis中，30分钟过期
        redisTemplate.opsForValue().set(token,jsonString,30, TimeUnit.MINUTES);*/


        //1.先查询当前登录账户所属的员工
        Employee admin = null;
        if (logininfo.getType() == 0){  //0 代表管理员 1普通用户  2平台员工
             admin = employeeMapper.findByLogininfoId(logininfo.getId());
        }

        //2.登录用户不是前台普通用户，需要查询他的菜单和权限
        List<Menu> ownMenus = new ArrayList<>();
        List<String> ownPermissions = new ArrayList<>();
        if (logininfo.getType() != 1){
            //查询当前登录用户拥有的菜单：手动组装树形结构
            ownMenus = menuService.findByLogininfoId(logininfo.getId());
            //查询当前登录用户拥有的权限
            ownPermissions = permissionMapper.findByLogininfoId(logininfo.getId());

        }

        //3.封装一个LogininfoDto
        LogininfoDto logininfoDto = new LogininfoDto(logininfo, admin, ownMenus, ownPermissions);

        //4.利用工具类生成JWT字符串
        PrivateKey privateKey = RsaUtils.getPrivateKey(JwtUtils.class.getClassLoader().getResource(this.privateKey).getFile());
        String token = JwtUtils.generateTokenExpireInMinutes(logininfoDto, privateKey, 30);//设置为30分钟有效期

        //将token和当前登录用户对象、员工数据、菜单及权限信息一起返回给前端
        Map<String,Object> map = new HashMap<>();
        map.put("userToken",token);
        map.put("loginUser",logininfo);
        map.put("admin",admin);
        map.put("menus",ownMenus);
        map.put("permissions",ownPermissions);
        return AjaxResult.success(map);
    }


    @Value("${wechat.appid}")
    private String appid;
    @Value("${wechat.appSecret}")
    private String appSecret;
    @Value("${wechat.tokenUrl}")
    private String tokenUrl;
    @Autowired
    private WxuserMapper wxuserMapper;

    /**
     * 通过code授权码换取access_token
     * @param code
     * @return
     */
    @Override
    public AjaxResult wechatLogin(String code) {
        try {
            //1.利用Java程序发送HTTP请求，访问微信开放平台接口：code换token
            String tokenUrl = this.tokenUrl
                    .replace("APPID",appid)
                    .replace("SECRET",appSecret)
                    .replace("CODE",code);
            String resultJson = HttpUtil.post(tokenUrl, "");//这里是访问微信开发平台接口
            System.out.println(resultJson);

            //2.json字符串转实体类
            TokenResultDto tokenResultDto = JSON.parseObject(resultJson, TokenResultDto.class);
            //判断errcode和errmsg是否存在，不存在最好
            Assert.isTrue(tokenResultDto.getErrcode() == null &&
                    !StringUtils.hasText(tokenResultDto.getErrmsg()),"code换取access_token失败！");

            //3.通过openid和unionid查询t_wxuser表  连表查询+自定义resultMap标签
            Wxuser wxUser = wxuserMapper.findByOpenidAndUnionid(tokenResultDto);

            if (wxUser == null){
                //没有查询到数据：说明用户是第一次使用微信扫码登录我们宠物之家平台，需要绑定手机号
                AjaxResult ajaxResult = AjaxResult.error("unbind");//"unionid":"ovnCWw7R0OnAEpuTk5K7CDZjx7cE"
                ajaxResult.setData(tokenResultDto);
                return ajaxResult;
            }else {
                //如果扫码返回的openid(微信用户唯一标识)不等于空，说明他已经登录过一次了，所以不用绑定了，取出关联的对象
                Logininfo logininfo = wxUser.getLogininfo();
                //走登录成功的流程了
                return loginSuccess(logininfo);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return AjaxResult.error("微信登录失败！！！");
    }


    @Value("${wechat.wechatuserinfoUrl}")
    private String wechatuserinfoUrl;

    @Autowired
    private UserMapper userMapper;
    /**
     * 微信用户绑定手机号
     * @param userDto
     * @return
     */
    @Transactional
    @Override
    public AjaxResult wechatUserBind(UserDto userDto) {
        try {
            //1.校验参数、图片验证码
            Assert.isTrue(StringUtils.hasText(userDto.getPhone()), "手机号不能为空！");
            //手机号格式
            Pattern pattern = Pattern.compile("^1[3-9]{1}[0-9]{9}$");
            Assert.isTrue(pattern.matcher(userDto.getPhone()).find(),"您输入的电话格式不正确");

            //图形验证码及其key(32位字符串)都不能为空
            Assert.isTrue(StringUtils.hasText(userDto.getImageCode()),"图形验证码");
            Assert.isTrue(StringUtils.hasText(userDto.getImageCodeKey()),"图形验证码的key不能为空");

            //检查图形是否正确：先使用key从redis中获取一个验证码
            Assert.isTrue(redisTemplate.hasKey(userDto.getImageCodeKey()),"图形验证码已失效");//；判断redis中是否存在

            System.out.println( redisTemplate.hasKey(userDto.getImageCodeKey())+ "cff");

            Object imageKey = redisTemplate.opsForValue().get(userDto.getImageCodeKey());//获取图形验证码
            Assert.isTrue(userDto.getImageCode().equals(imageKey),"图形验证码错误！");

            //2.校验手机号是否已经注册过：通过用户提交的手机号来查询t_user表，能查询到表示已经注册过，就不允许注册了
            User findusername = userMapper.findusername(userDto.getPhone());
            Assert.isTrue(findusername==null,"用户名已存在！！！");

            //携带access_token去调用微信开放平台，得到微信用户的个人信息
            String wechatuserinfourl = this.wechatuserinfoUrl
                    .replace("OPENID", appid)
                    .replace("ACCESS_TOKEN", userDto.getAccess_token());
            String jsonResultUserinfo = HttpUtil.get(wechatuserinfourl);
            UserinfoDto userinfoDto = JSON.parseObject(jsonResultUserinfo, UserinfoDto.class);
            System.out.println("小菜=》" + userinfoDto);

            //判断前端返回的对象中是否存在错误码和错误提示
            Assert.isTrue(userinfoDto.getErrcode() == null
            && !StringUtils.hasText(userinfoDto.getErrmsg()),"获取微信用户个人信息失败！");

            //添加没有外键的表t_logininfo
            Logininfo logininfo = new Logininfo();
            logininfo.setUsername(userDto.getPhone());
            logininfo.setPhone(userDto.getPhone());
            //盐值：MD5加密的时候洒了一把盐，目的是增加密码的安全性
            //UUID：生成绝对唯一的字符串
            String salt = UUID.randomUUID().toString();
            logininfo.setSalt(salt);
            //MD5加密,
            String md5Password = MD5Utils.encrypByMd5(userDto.getPassword() + salt);
            logininfo.setPassword(md5Password);
            logininfo.setType(1);
            logininfo.setHeadImg(userinfoDto.getHeadimgurl());
            logininfoMapper.add(logininfo);

            //再添加t_user表的数据
            User user = new User();
            user.setUsername(userDto.getPhone());
            user.setPhone(userDto.getPhone());
            user.setSalt(salt);
            user.setPassword(md5Password);
            user.setHeadImg(userinfoDto.getHeadimgurl());
            user.setLogininfo_id(logininfo.getId());
            userMapper.add(user);
            //最后添加t_wxchat表  昵称、性别、地址、头像来自于微信开放平台  openid和unionid来自于前端表单
            Wxuser wxuser = new Wxuser();
            wxuser.setOpenid(userinfoDto.getOpenid());
            wxuser.setNickname(userinfoDto.getNickname());
            wxuser.setSex(userinfoDto.getSex());
            wxuser.setAddress(userinfoDto.getCountry() + userinfoDto.getProvince() + userinfoDto.getCity());
            wxuser.setHeadimgurl(userinfoDto.getHeadimgurl());
            wxuser.setUnionid(userinfoDto.getUnionid());
            wxuser.setUser_id(user.getId());
            wxuserMapper.add(wxuser);
            //登录成功后的流程
            return loginSuccess(logininfo);
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("微信绑定失败！" + e.getMessage());
        }

    }

    /**
     * 用户短信验证码登录
     * @param userDto
     * @return
     */
    @Override
    public AjaxResult smsLogin(UserDto userDto) {
        //校验参数
        Assert.isTrue(StringUtils.hasText(userDto.getPhone()),"请输入电话号码");
        //校验电话号码格式
        Pattern pattern = Pattern.compile("^1[3-9]{1}[0-9]{9}$");
        Assert.isTrue(pattern.matcher(userDto.getPhone()).find(),"您输入的电话格式不正确");
        //图形验证码及其key(32位字符串)都不能为空
        Assert.isTrue(StringUtils.hasText(userDto.getImageCode()),"请输入图形验证码");
        Assert.isTrue(StringUtils.hasText(userDto.getImageCodeKey()),"图形验证码的key不能为空");

        //检查图形是否正确：先使用key从redis中获取一个验证码
        Assert.isTrue(redisTemplate.hasKey(userDto.getImageCodeKey()),"图形验证码已失效");//；判断redis中是否存在

        Object imageKey = redisTemplate.opsForValue().get(userDto.getImageCodeKey());//获取图形验证码
        Assert.isTrue(userDto.getImageCode().equals(imageKey),"图形验证码错误！");

        //短信验证码
        Assert.isTrue(StringUtils.hasText(userDto.getSmsCode()),"图形验证码不能为空");
        //查询t_logininfo表是否存在该用户
        Logininfo logininfo = logininfoMapper.findByPhone(userDto.getPhone());
        Assert.isTrue(logininfo != null,"用户名不存在，请先注册");

        //登录成功的流程
        try {
            return loginSuccess(logininfo);
        } catch (Exception e) {
            e.printStackTrace();
        }


        return AjaxResult.error("短信登录失败！");
    }

    /**
     * 后台登录
     * @param userDto
     * @return
     */
    @Override
    public AjaxResult backstageLogin(UserDto userDto) {
        //校验参数
        Assert.isTrue(StringUtils.hasText(userDto.getAccount()),"账号不能为空！");
        Assert.isTrue(StringUtils.hasText(userDto.getPassword()),"密码不能为空！");

        //查询t_logininfo账号和密码是否存在
        Logininfo logininfo = logininfoMapper.findByaccountAndpassword(userDto);

       Assert.isTrue(logininfo != null,"用户名不存在或者账号密码错误！");

        //判断密码
        String md5Pssword = MD5Utils.encrypByMd5(userDto.getPassword() + logininfo.getSalt());
        Assert.isTrue(logininfo.getPassword().equals(md5Pssword),"账号或者密码错误！！！");

        try {
            return loginSuccess(logininfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return AjaxResult.error("后台登录失败！");
    }


}
